package editor.headers;

import editor.ErrorHandler;
import editor.Images;
import editor.NotImplementedException;
import editor.PwnIDE;
import editor.code.ASMCodePanel;
import editor.code.CodePanel;
import editor.code.LineField;
import editor.widgets.*;
import model.Segment;
import model.caret.CaretObserver;
import model.caret.CaretRef;
import model.caret.HeaderCaret;
import model.code.*;
import model.code.line.SingleLine;
import model.symbol.Constant;
import model.symbol.LocalVariable;
import model.symbol.StructureMember;
import observer.ListObserver;
import observer.ObservedList;
import perf.PerformanceTestInfo;
import perf.PerformanceTestOptions;
import widgets.ActionInputListener;
import widgets.CustomButton;
import widgets.ImageButton;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

import static editor.Configuration.*;
import static model.code.CodeFileChunk.*;

/**
 * This is the superclass of headers for files, sections, functions, structures, constants, variables, etc,
 * each of which has a subclass.
 *
 * FEATURE: Dragging and dropping
 *
 * @author Neil Dickson
 */
public final class Header extends JComponent implements ComponentListener, AncestorListener, CaretObserver, MouseListener {
	private final	CodeFileChunk				code;
	private final	ArrayList<HeaderComponent>	components;
	private final	int[]						allowedComponents;

	private static Color		SELECTION_COLOUR	= new Color(0xB0C8E8);

	public static final Color	TRANSPARENT_COLOUR	= new Color(0,0,0,0);

	public static int			MARGIN				= 4;
	private static int			PADDING				= 2;

	public static Border		TEXT_FIELD_BORDER	= new LineBorder(new Color(0xE0E0E0));//new EmptyBorder(0,0,0,0);
	public static Font			SECTION_NAME_FONT	= new Font("Cambria",Font.BOLD,20);
	public static FontMetrics	SECTION_NAME_METRICS= PwnIDE.editor.getFontMetrics(SECTION_NAME_FONT);
	public static Font			NAME_FONT			= new Font("Cambria",Font.BOLD,16);
	public static FontMetrics	NAME_METRICS		= PwnIDE.editor.getFontMetrics(NAME_FONT);
	public static Font			LABEL_FONT			= new Font("Calibri",Font.BOLD,14);
	public static Font			OTHER_FONT			= new Font("Calibri",0,12);
	public static FontMetrics	OTHER_METRICS		= PwnIDE.editor.getFontMetrics(OTHER_FONT);

	public static final int[]	ALLOWED_CODE_FILE_COMPONENTS = {LOCATION_DESCRIPTION,LOCATION_AUTHORS,LOCATION_SEE_ALSO};
	public static final int[]	ALLOWED_SECTION_COMPONENTS = {LOCATION_DESCRIPTION,LOCATION_SEGMENT,LOCATION_SEE_ALSO};
	public static final int[]	ALLOWED_FUNCTION_COMPONENTS = {LOCATION_DESCRIPTION,LOCATION_PARAMETERS,LOCATION_LOCAL_VARS,LOCATION_RETURNS,LOCATION_MODIFIERS,LOCATION_SEE_ALSO};
	public static final int[]	ALLOWED_STRUCTURE_COMPONENTS = {LOCATION_DESCRIPTION,LOCATION_SEE_ALSO};
	public static final int[]	ALLOWED_VARIABLE_COMPONENTS = {LOCATION_DESCRIPTION,LOCATION_MODIFIERS,LOCATION_SEE_ALSO};
	public static final int[]	ALLOWED_CONSTANTS_COMPONENTS = {LOCATION_DESCRIPTION,LOCATION_INCREMENT,LOCATION_SEE_ALSO};
	public static final int[]	ALLOWED_PROTOTYPES_COMPONENTS = {LOCATION_DESCRIPTION,LOCATION_SEE_ALSO};
	public static final int[]	ALLOWED_OTHER_COMPONENTS = {};

	public boolean wasSelected;

	public Header(CodeFileChunk code) {
		this.code = code;
		components = new ArrayList<HeaderComponent>();
		setLayout(null);
		setBackground(BG_COLOUR.getColour());

		NamePanel namePanel = new NamePanel(this);
		components.add(namePanel);
		namePanel.button.addMouseListener(this);
		// NOTE: Most sections (that aren't files) don't really need a description because the name describes it.
		if (!(code instanceof Section) || code instanceof CodeFile) {
			components.add(new DescriptionPanel(this));
		}
		if (code instanceof CodeFile) {
			ObservedList<Rowable> seeAlso = getSeeAlsoList(code);
			if (seeAlso.size()>0) {
				components.add(new SeeAlsoList(seeAlso,this));
			}
			components.add(new AuthorTable(this));
			components.add(new ContentPanel(this));
			allowedComponents = ALLOWED_CODE_FILE_COMPONENTS;
		}
		else if (code instanceof Function) {
			Function function = (Function)code;
			//components.add(new ParameterTable(this));
			if (function.getTestFunctionInfo()!=null && function.getTestFunctionInfo() instanceof PerformanceTestInfo) {
				components.add(new PerformanceTestOptions(this,function.getTestFunctionInfo()));
			}
			else {
				components.add(new ParameterTable(this));
			}
			components.add(new LocalVariableTable(this));
			components.add(new ReturnTable(this));
			if (	function.getOffset().length()!=0 ||
					function.getAlignment().get()!=CodeFileChunk.DEFAULT_ALIGNMENT_MARKER ||
					function.getAccessScopeType().length()!=0 ||
					function.isExported() ||
					function.getCallingConvention().length()!=0) {
				components.add(new ModifierPanel(this));
			}
			ObservedList<Rowable> seeAlso = getSeeAlsoList(code);
			if (seeAlso.size()>0) {
				components.add(new SeeAlsoList(seeAlso,this));
			}
			components.add(new CodePanelWrapper(this));
			allowedComponents = ALLOWED_FUNCTION_COMPONENTS;
		}
		else if (code instanceof Structure) {
			components.add(new StructureMemberTable(this));
			allowedComponents = ALLOWED_STRUCTURE_COMPONENTS;
		}
		else if (code instanceof GlobalVariable) {
			GlobalVariable variable = (GlobalVariable)code;
			if (	variable.getOffset().length()!=0 ||
					variable.getAlignment().get()!=CodeFileChunk.DEFAULT_ALIGNMENT_MARKER ||
					variable.getAccessScopeType().length()!=0) {
				components.add(new ModifierPanel(this));
			}
			components.add(new VariablePieceTable(this));
			allowedComponents = ALLOWED_VARIABLE_COMPONENTS;
		}
		else if (code instanceof Constants) {
			components.add(new IncrementField(this));
			components.add(new ConstantTable(this));
			allowedComponents = ALLOWED_CONSTANTS_COMPONENTS;
		}
		else if (code instanceof Section) {
			if (code.getDescription().size()>1 || (code.getDescription().size()==1 && code.getDescription().get(0).length()!=0)) {
				components.add(new DescriptionPanel(this));
			}
			if (((Section)code).getSegmentName().length()!=0) {
				components.add(new SegmentField(this));
			}
			ObservedList<Rowable> seeAlso = getSeeAlsoList(code);
			if (seeAlso.size()>0) {
				components.add(new SeeAlsoList(seeAlso,this));
			}
			components.add(new ContentPanel(this));
			allowedComponents = ALLOWED_SECTION_COMPONENTS;
		}
		else if (code instanceof Prototypes) {
			allowedComponents = ALLOWED_PROTOTYPES_COMPONENTS;
		}
		else if (code instanceof PreprocessorLine) {
			System.err.println("Trying to make a Header for a PreprocessorLine in "+code.getFile()+", but that's not yet supported.\nThis is not an error popup in order to avoid tons of useless popups while creating the UI.");
			allowedComponents = ALLOWED_OTHER_COMPONENTS;
		}
		else {
			ErrorHandler.showNotImplementedMessage("Sorry, an error has occurred, but this one should be easy\nto fix if you submit the following text as a bug report.\nAn unknown subclass \""+code.getClass()+"\" of CodeFileChunk was encountered when creating a Header for it.");
			allowedComponents = ALLOWED_OTHER_COMPONENTS;
		}
		if (!(code instanceof Section || code instanceof Function)) {
			ObservedList<Rowable> seeAlso = getSeeAlsoList(code);
			if (seeAlso!=null && (seeAlso.size()>0 || code instanceof Prototypes)) {
				components.add(new SeeAlsoList(seeAlso,this));
			}
		}
		CodeFile file = code.getFile();
		if (file!=null) {
			file.getCaretRef().addObserver(this);
		}
		for (HeaderComponent component : components) {
			add(component);
		}
	}

	public static ObservedList<Rowable> getSeeAlsoList(CodeFileChunk code) {
		if (code instanceof Section) {
			return ((Section)code).getSeeAlso();
		}
		else if (code instanceof Function) {
			return ((Function)code).getSeeAlso();
		}
		else if (code instanceof Constants) {
			return ((Constants)code).getSeeAlso();
		}
		else if (code instanceof GlobalVariable) {
			return ((GlobalVariable)code).getSeeAlso();
		}
		else if (code instanceof Structure) {
			return ((Structure)code).getSeeAlso();
		}
		else if (code instanceof Prototypes) {
			return ((Prototypes)code).getFunctionNames();
		}
		return null;
	}

	protected void paintComponent(Graphics g) {
		boolean selected = isSelected();
		g.setColor(selected ? SELECTION_COLOUR : BG_COLOUR.getColour());
		g.fillRect(0,0,getWidth(),getHeight());
		if (!(code instanceof CodeFile)) {
			g.setColor(EDGE_COLOUR.getColour());
			g.drawLine(0,0,getWidth(),0);
		}
		if (code instanceof Function && getCodePanelWrapper().isExpanded) {
			int headerHeight = getHeaderHeight();
			g.setColor(INNER_EDGE_COLOUR.getColour());
			g.drawLine(0,headerHeight-1,getWidth(),headerHeight-1);
		}
		wasSelected = selected;
	}

	/**
	 * @return the height of the header part of the header
	 */
	public int getHeaderHeight() {
		HeaderComponent c = null;
		if (code instanceof Function) {
			c = getCodePanelWrapper();
		}
		if (code instanceof Section) {
			c = getContentPanel();
		}
		return (c!=null) ? c.getY() : getHeight();
	}

	public CodePanel getCodePanel() {
		CodePanelWrapper c = getCodePanelWrapper();
		return (c!=null) ? c.codePanel : null;
	}

	public CodePanelWrapper getCodePanelWrapper() {
		// NOTE: The loop is in reverse order because the CodePanelWrapper should always come last for the time being.
		for (int i=components.size()-1;i>=0;--i) {
			if (components.get(i) instanceof CodePanelWrapper) {
				return (CodePanelWrapper)components.get(i);
			}
		}
		return null;
	}

	public ContentPanel getContentPanel() {
		// NOTE: The loop is in reverse order because the ContentPanel should always come last for the time being.
		for (int i=components.size()-1;i>=0;--i) {
			if (components.get(i) instanceof ContentPanel) {
				return (ContentPanel)components.get(i);
			}
		}
		return null;
	}

	public HeaderComponent getComponentOfType(int type) {
		for (HeaderComponent component : components) {
			if (component.getType()==type) {
				return component;
			}
		}
		return null;
	}

	public Insets getInsets() {
		return new Insets(0,0,0,0);
	}

	public Insets getInsets(Insets insets) {
		insets.bottom = 0;
		insets.right = 0;
		insets.left = 0;
		insets.top = 0;
		return insets;
	}

	public boolean isSelected() {
		CodeFile file = code.getFile();
		if (file==null) {
			return false;
		}
		CaretRef caretRef = file.getCaretRef();
		return caretRef.isHeaderCaret() && ((HeaderCaret)caretRef.get()).contains(code);
	}

	public void componentResized(ComponentEvent e) {
		doFullLayout();
	}
	public void componentMoved(ComponentEvent e) {}
	public void componentShown(ComponentEvent e) {}
	public void componentHidden(ComponentEvent e) {}

	public void ancestorAdded(AncestorEvent event) {
		CodeFile file = code.getFile();
		if (file!=null) {
			file.getCaretRef().addObserver(this);
		}
	}

	public void ancestorRemoved(AncestorEvent event) {
		CodeFile file = code.getFile();
		if (file!=null) {
			file.getCaretRef().removeObserver(this);
		}
	}

	public void ancestorMoved(AncestorEvent event) {
		throw new NotImplementedException();
	}

	public void caretChanged(CaretRef caretRef) {
		boolean selected = isSelected();
		if (selected!=wasSelected) {
			repaint();
		}
	}

	public void addComponent(HeaderComponent component) {
		// Find where to add the component (default is at the end if no other components)
		int index = getComponentCount();
		for (int i=0;i<getComponentCount();++i) {
			Component c = getComponent(i);
			// Replace any component of equal location type
			if (c instanceof HeaderComponent && ((HeaderComponent)c).getType()==component.getType()) {
				components.remove(i);
				remove(i);
				index = i;
				break;
			}
			// Right before the first component with larger location type
			if (c instanceof HeaderComponent && ((HeaderComponent)c).getType()>component.getType()) {
				index = i;
				break;
			}
		}
		components.add(index,component);
		add(component,index);
		doFullLayout();
	}

	public void removeComponent(int locationType) {
		// Find from where to remove the component
		for (int i=0;i<getComponentCount();++i) {
			Component c = getComponent(i);
			if (c instanceof HeaderComponent && ((HeaderComponent)c).getType()==locationType) {
				components.remove(i);
				remove(i);
				doFullLayout();
				break;
			}
		}
	}

	public void mouseClicked(MouseEvent e) {
		if (e.getButton()==MouseEvent.BUTTON3 && e.getClickCount()==1) {
			JPopupMenu menu = new JPopupMenu();
			for (final int allowedType : allowedComponents) {
				boolean isAlreadyShown = false;
				for (int i=0;i<getComponentCount();++i) {
					Component c = getComponent(i);
					if (c instanceof HeaderComponent && ((HeaderComponent)c).getType()==allowedType) {
						isAlreadyShown = true;
						break;
					}
				}
				final boolean isShown = isAlreadyShown;
				String text = "(unknown field type)";
				switch (allowedType) {
					case LOCATION_DESCRIPTION:		text = "Description";		break;
					case LOCATION_AUTHORS:			text = "Authors";			break;
					case LOCATION_SEE_ALSO:			text = "See Also";			break;
					case LOCATION_SEGMENT:			text = "Segment";			break;
					case LOCATION_PARAMETERS:		text = "Parameters";		break;
					case LOCATION_LOCAL_VARS:		text = "Local Variables";	break;
					case LOCATION_RETURNS:			text = "Return Values";		break;
					case LOCATION_MODIFIERS:		text = "Modifiers";			break;
//					case LOCATION_STRUCTURE_MEMBERS:text = "Structure Members";	break;
//					case LOCATION_VAR_PIECES:		text = "Variable Pieces";	break;
					case LOCATION_INCREMENT:		text = "Increment";			break;
//					case LOCATION_CONSTANTS_MEMBERS:text = "Constants";			break;
				}
				JCheckBoxMenuItem item = new JCheckBoxMenuItem(text,isShown);
				menu.add(item);
				item.addActionListener(new ActionListener(){
					public void actionPerformed(ActionEvent e) {
						// Double-check to make sure that it's still shown / not shown as before
						boolean isCurrentlyShown = false;
						HeaderComponent component = null;
						for (int i=0;i<getComponentCount();++i) {
							Component c = getComponent(i);
							if (c instanceof HeaderComponent && ((HeaderComponent)c).getType()==allowedType) {
								isCurrentlyShown = true;
								component = (HeaderComponent)c;
								break;
							}
						}
						// Don't do anything if it's already been changed
						if (isCurrentlyShown!=isShown) {
							return;
						}
						if (isCurrentlyShown) {
							components.remove(component);
							remove(component);
							doFullLayout();
						}
						else {
							Header h = Header.this;
							switch (allowedType) {
								case LOCATION_DESCRIPTION:		component = new DescriptionPanel(h);		break;
								case LOCATION_AUTHORS:			component = new AuthorTable(h);				break;
								case LOCATION_SEE_ALSO:			component = new SeeAlsoList(getSeeAlsoList(code),h);break;
								case LOCATION_SEGMENT:			component = new SegmentField(h);			break;
								case LOCATION_PARAMETERS:		component = new ParameterTable(h);			break;
								case LOCATION_LOCAL_VARS:		component = new LocalVariableTable(h);		break;
								case LOCATION_RETURNS:			component = new ReturnTable(h);				break;
								case LOCATION_MODIFIERS:		component = new ModifierPanel(h);			break;
//								case LOCATION_STRUCTURE_MEMBERS:component = new StructureMemberTable(h);	break;
//								case LOCATION_VAR_PIECES:		component = new VariablePieceTable(h);		break;
								case LOCATION_INCREMENT:		component = new IncrementField(h);			break;
//								case LOCATION_CONSTANTS_MEMBERS:component = new IncrementField(h);			break;
								default: component = null;
							}
							if (component!=null) {
								// Find where to add the component (default is at the end if no other components)
								int index = getComponentCount();
								for (int i=0;i<getComponentCount();++i) {
									Component c = getComponent(i);
									// Right before the first component with larger location type
									if (c instanceof HeaderComponent && ((HeaderComponent)c).getType()>component.getType()) {
										index = i;
										break;
									}
								}
								components.add(index,component);
								add(component,index);
								doFullLayout();
							}
							else {
								ErrorHandler.showNotImplementedMessage("For some unknown reason adding a header field of type "+allowedType+" doesn't work.\nCould you please report this, since it's a silly mistake on my part that should be very easy to fix.");
							}
						}
					}
				});
			}
			menu.show(this,e.getX(),e.getY());
		}
	}
	public void mousePressed(MouseEvent e) {}
	public void mouseReleased(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}

	public final static class TextLabel extends JTextField {
		public TextLabel(String text,Color backgroundColour) {
			super(text);
			setFont(LABEL_FONT);
			setForeground(TEXT_COLOUR.getColour());
			setBackground(backgroundColour);
			setBorder(null);
			setOpaque(backgroundColour.getAlpha()==255);
			setEditable(false);
			setFocusable(false);
			FontMetrics font = getFontMetrics(LABEL_FONT);
			setSize(new Dimension(font.stringWidth(text)+2,font.getHeight()+2));
			setMinimumSize(new Dimension(font.stringWidth(text)+2,font.getHeight()+2));
			setPreferredSize(new Dimension(font.stringWidth(text)+2,font.getHeight()+2));
			// For some reason, providing Short.MAX_VALUE indicates that it can be any width, but left-aligns it in a managed layout... *sigh*
			setMaximumSize(new Dimension(Short.MAX_VALUE,font.getHeight()+2));
		}
	}

	public CodeFileChunk getCode() {
		return code;
	}

	public void doFullLayout() {
		Container c = getParent();
		Header topHeader = this;
		while (c!=null && !(c instanceof CustomScrollPane)) {
			if (c instanceof Header) {
				topHeader = (Header)c;
			}
			c = c.getParent();
		}
		if (c!=null) {
			if (topHeader.getParent().getLayout()!=null) {
				topHeader.getParent().setLayout(null);
			}
			topHeader.doLayout(((CustomScrollPane)c).getViewport().width);
			if (!topHeader.getSize().equals(topHeader.getParent().getSize())) {
				Dimension dim = topHeader.getSize();
				topHeader.getParent().setSize(dim);
				topHeader.getParent().setMinimumSize(dim);
				topHeader.getParent().setPreferredSize(dim);
			}
		}
	}

	public void revalidate() {
		doFullLayout();
	}

	public void doLayout(int viewportWidth) {
		// Find the full width (must at least fill the viewport)
		int width = Math.max(viewportWidth,getMinWidth());
		// Then place and lay out the components
		int y=MARGIN;
		for (HeaderComponent component : components) {
			if (component instanceof ContentPanel || component instanceof CodePanelWrapper) {
				component.setLocation(0,y);
				component.doLayout(width);
			}
			else {
				component.setLocation(MARGIN,y);
				component.doLayout(width-MARGIN*2);
			}
			y += component.getHeight()+PADDING;
		}
		setSize(width,y-PADDING+MARGIN);
	}

	public int getMinWidth() {
		int width = 0;
		for (HeaderComponent component : components) {
			width = Math.max(width,component.getMinWidth());
		}
		return width+MARGIN*2;
	}

	public static Image getIcon(CodeFileChunk chunk) {
		if (chunk instanceof Function) return Images.FUNCTION;
		if (chunk instanceof Structure) return Images.STRUCTURE;
		if (chunk instanceof Constants) return Images.CONSTANTS;
		if (chunk instanceof GlobalVariable) return Images.VARIABLE;
		if (chunk instanceof Prototypes) return Images.PROTOTYPES;
		if (chunk instanceof CodeFile) return ((CodeFile)chunk).getIcon().getImage();
		if (chunk instanceof Section) return Images.SECTION;
		throw new NotImplementedException();
	}

	public final class NamePanel extends HeaderComponent implements ActionInputListener {
		final ImageButton	button;
		final LineField		nameField;
		public NamePanel(Header header) {
			super(header);
			button = new ImageButton(getIcon(code));
			nameField = new LineField(code.getName(),code.getFile().getCaretRef(),true);
			if (code instanceof Section) {
				nameField.setFont(SECTION_NAME_FONT);
				nameField.setForeground(SECTION_NAME_COLOUR.getColour());
			}
			else {
				nameField.setFont(NAME_FONT);
				nameField.setForeground(NAME_COLOUR.getColour());
			}
			nameField.setBorder(TEXT_FIELD_BORDER);
			nameField.setBackground(TRANSPARENT_COLOUR);
			nameField.addComponentListener(this);
			button.addActionInputListener(this);
			add(button);
			add(nameField);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_NAME;
		}
		public void doLayout(int width) {
			button.setLocation(0,0);
			int height = button.getHeight();
			if (nameField!=null) {
				nameField.removeComponentListener(this);
				nameField.setBounds(button.getWidth(),0,width-button.getWidth(),nameField.getRowHeight());
				nameField.addComponentListener(this);
				if (height<nameField.getHeight()) {
					height = nameField.getHeight();
				}
			}
			setSize(width,height);
		}
		public int getMinWidth() {
			return button.getWidth()+((nameField!=null)?nameField.getMinimumSize().width:0);
		}
		public void actionPerformed(InputEvent ie) {
			if (ie instanceof MouseEvent) {
				MouseEvent e = (MouseEvent)ie;
				// On a left click, select the Header (or toggle its selection if shift is down)
				if (e.getButton()==MouseEvent.BUTTON1 && !(code instanceof CodeFile)) {
					CaretRef caretRef = code.getFile().getCaretRef();
					HeaderCaret caret;
					if (caretRef.isHeaderCaret()) {
						caret = (HeaderCaret)caretRef.get();
					}
					else {
						caret = new HeaderCaret(caretRef);
						caretRef.set(caret);
					}
					if (e.isShiftDown()) {
						caret.toggleSelected(getCode());
					}
					else {
						caret.removeAllBut(getCode());
					}
					if (isSelected() && nameField!=null) {
						nameField.requestFocus();
					}
					Container container = getParent();
					while (container!=null && !(container instanceof CustomScrollPane)) {
						container = container.getParent();
					}
					if (container!=null) {
						container.repaint();
					}
				}
			}
		}
	}

	public final class DescriptionPanel extends HeaderComponent {
		final LineField	description;
		public DescriptionPanel(Header header) {
			super(header);
			// NOTE: The initial number of rows or columns shouldn't matter.
			description = new LineField(code.getDescription(),code.getFile().getCaretRef(),false);
			description.setFont(OTHER_FONT);
			description.setForeground(TEXT_COLOUR.getColour());
			description.setBackground(TRANSPARENT_COLOUR);
			description.setBorder(TEXT_FIELD_BORDER);
//			description.setLineWrap(true);
//			description.setWrapStyleWord(true);
			description.addComponentListener(this);
			add(description);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_DESCRIPTION;
		}
		public void doLayout(int width) {
			description.removeComponentListener(this);
			description.setLocation(0,0);
			description.setSize(width,description.getMinimumSize().height);
			description.addComponentListener(this);
			// NOTE: Column width is determined by the width of lowercase letter M.
//			description.setColumns(width/description.getFontMetrics(description.getFont()).charWidth('m'));
			setSize(width,description.getHeight());
		}
		public int getMinWidth() {
//			FontMetrics font = description.getFontMetrics(description.getFont());
//			// It should at least be wide enough to contain a uppercase letter W
//			int width = font.charWidth('W');
//			String[] lines = StringUtil.getLines(description.getText());
//			for (String line : lines) {
//				width = Math.max(width,font.stringWidth(line));
//			}
//			return width;
			return description.getMinimumSize().width;
		}
	}

	public final class SeeAlsoList extends HeaderComponent {
		final ObservedStringTable<Rowable>	seeAlsoField;
		public SeeAlsoList(ObservedList<Rowable> list,Header header) {
			super(header);
			String text = (code instanceof Prototypes) ? "Prototypes:" : "See Also:";
			seeAlsoField = new ObservedStringTable<Rowable>(code,text,list,Rowable.SEE_ALSO_DUMMY);
			seeAlsoField.setFont(OTHER_FONT);
			seeAlsoField.setForeground(TEXT_COLOUR.getColour());
			seeAlsoField.setBackground(TRANSPARENT_COLOUR);
			seeAlsoField.addComponentListener(this);
			add(seeAlsoField);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_SEE_ALSO;
		}
		public void doLayout(int width) {
			seeAlsoField.removeComponentListener(this);
			seeAlsoField.setLocation(0,0);
			seeAlsoField.doLayout();
			seeAlsoField.addComponentListener(this);
			setSize(seeAlsoField.getSize());
		}
		public int getMinWidth() {
			return seeAlsoField.getWidth();
		}
	}

	public static final class ParameterTable extends HeaderComponent {
		final ObservedStringTable<LocalVariable>	parametersField;
		public ParameterTable(Header header) {
			super(header);
			int mode = header.code.getMode();
			String defaultType = (mode==64) ? "QWORD" : ((mode==32) ? "DWORD" : "WORD");
			parametersField = new ObservedStringTable<LocalVariable>(header.code,"Parameters:",((Function)header.code).getParameters(),LocalVariable.DUMMY_PARAM,new String[]{"",defaultType,""});
			parametersField.setFont(OTHER_FONT);
			parametersField.setForeground(TEXT_COLOUR.getColour());
			parametersField.setBackground(TRANSPARENT_COLOUR);
			parametersField.addComponentListener(this);
			add(parametersField);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_PARAMETERS;
		}
		public void doLayout(int width) {
			parametersField.removeComponentListener(this);
			parametersField.setLocation(0,0);
			parametersField.doLayout();
			parametersField.addComponentListener(this);
			setSize(parametersField.getSize());
		}
		public int getMinWidth() {
			return parametersField.getWidth();
		}
	}

	public final class LocalVariableTable extends HeaderComponent {
		final ObservedStringTable<LocalVariable>	localVariablesField;
		public LocalVariableTable(Header header) {
			super(header);
			int mode = code.getMode();
			String defaultType = (mode==64) ? "QWORD" : ((mode==32) ? "DWORD" : "WORD");
			localVariablesField = new ObservedStringTable<LocalVariable>(code,"Local Variables:",((Function)code).getLocalVariables(),LocalVariable.DUMMY_LOCAL_VAR,new String[]{"",defaultType,""});
			localVariablesField.setFont(OTHER_FONT);
			localVariablesField.setForeground(TEXT_COLOUR.getColour());
			localVariablesField.setBackground(TRANSPARENT_COLOUR);
			localVariablesField.addComponentListener(this);
			add(localVariablesField);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_LOCAL_VARS;
		}
		public void doLayout(int width) {
			localVariablesField.removeComponentListener(this);
			localVariablesField.setLocation(0,0);
			localVariablesField.doLayout();
			localVariablesField.addComponentListener(this);
			setSize(localVariablesField.getSize());
		}
		public int getMinWidth() {
			return localVariablesField.getWidth();
		}
	}

	public final class ReturnTable extends HeaderComponent {
		final ObservedStringTable<Function.Return>	returnsField;
		public ReturnTable(Header header) {
			super(header);
			int mode = code.getMode();
			String defaultType = (mode==64) ? "rax" : ((mode==32) ? "eax" : "ax");
			returnsField = new ObservedStringTable<Function.Return>(code,"Returns:",((Function)code).getReturns(), Function.Return.DUMMY,new String[]{defaultType,""});
			returnsField.setFont(OTHER_FONT);
			returnsField.setForeground(TEXT_COLOUR.getColour());
			returnsField.setBackground(TRANSPARENT_COLOUR);
			returnsField.addComponentListener(this);
			add(returnsField);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_RETURNS;
		}
		public void doLayout(int width) {
			returnsField.removeComponentListener(this);
			returnsField.setLocation(0,0);
			returnsField.doLayout();
			returnsField.addComponentListener(this);
			setSize(returnsField.getSize());
		}
		public int getMinWidth() {
			return returnsField.getWidth();
		}
	}

	public final class ModifierPanel extends HeaderComponent {
		final ObservedStringComboBox	scopeBox;
		final ObservedStringComboBox	callingConvBox;
		final ObservedIntComboBox		alignmentBox;
		final ObservedBooleanCheckBox	exportedBox;
		final LineField					offsetField;
		public ModifierPanel(Header header) {
			super(header);
			// NOTE: The combo boxes don't render properly with transparency
			if (code instanceof Function) {
				scopeBox = new ObservedStringComboBox(((Function)code).getAccessScopeType(),Function.ACCESS_SCOPE_TYPES);
			}
			else {	// code instanceof GlobalVariable
				scopeBox = new ObservedStringComboBox(((GlobalVariable)code).getAccessScopeType(),GlobalVariable.ACCESS_SCOPE_TYPES);
			}
			scopeBox.setBackground(BG_COLOUR.getColour());//TRANSPARENT_COLOUR);
			scopeBox.setForeground(TEXT_COLOUR.getColour());
			scopeBox.setFont(OTHER_FONT);
			if (code instanceof Function) {
				callingConvBox = new ObservedStringComboBox(((Function)code).getCallingConvention(),Function.CALLING_CONVENTION_NAMES);
				callingConvBox.setBackground(BG_COLOUR.getColour());//TRANSPARENT_COLOUR);
				callingConvBox.setForeground(TEXT_COLOUR.getColour());
				callingConvBox.setFont(OTHER_FONT);
			}
			else {
				callingConvBox = null;
			}
			if (code instanceof Function) {
				alignmentBox = new ObservedIntComboBox(((Function)code).getAlignment(),new int[]{CodeFileChunk.DEFAULT_ALIGNMENT_MARKER,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192});
			}
			else {	// code instanceof GlobalVariable
				alignmentBox = new ObservedIntComboBox(((GlobalVariable)code).getAlignment(),new int[]{CodeFileChunk.DEFAULT_ALIGNMENT_MARKER,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192});
			}
			alignmentBox.setBackground(BG_COLOUR.getColour());//TRANSPARENT_COLOUR);
			alignmentBox.setForeground(TEXT_COLOUR.getColour());
			alignmentBox.setFont(OTHER_FONT);
			if (code instanceof Function) {
				exportedBox = new ObservedBooleanCheckBox(((Function)code).getIsExported(),"Export?");
				exportedBox.setBackground(TRANSPARENT_COLOUR);
				exportedBox.setForeground(TEXT_COLOUR.getColour());
				exportedBox.setFont(OTHER_FONT);
			}
			else {
				exportedBox = null;
			}
			if (code instanceof Function) {
				offsetField = new LineField(((Function)code).getOffset(),code.getFile().getCaretRef(),true);
			}
			else {	// code instanceof GlobalVariable
				offsetField = new LineField(((GlobalVariable)code).getOffset(),code.getFile().getCaretRef(),true);
			}
			offsetField.setBorder(TEXT_FIELD_BORDER);
			offsetField.setBackground(TRANSPARENT_COLOUR);
			offsetField.setForeground(TEXT_COLOUR.getColour());
			offsetField.setFont(OTHER_FONT);
			add(scopeBox);
			if (callingConvBox!=null) {
				add(callingConvBox);
			}
			add(alignmentBox);
			if (exportedBox!=null) {
				add(exportedBox);
			}
			add(offsetField);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_MODIFIERS;
		}
		public void doLayout(int width) {
			int x = 0;
			int height = 0;
			if (scopeBox.isVisible()) {
				if (scopeBox.getWidth()==0 || scopeBox.getHeight()==0) {
					scopeBox.setSize(scopeBox.getPreferredSize());
				}
				scopeBox.setLocation(x,0);
				x += scopeBox.getWidth();
				height = Math.max(height,scopeBox.getHeight());
			}
			if (callingConvBox!=null && callingConvBox.isVisible()) {
				if (callingConvBox.getWidth()==0 || callingConvBox.getHeight()==0) {
					callingConvBox.setSize(callingConvBox.getPreferredSize());
				}
				callingConvBox.setLocation(x,0);
				x += callingConvBox.getWidth();
				height = Math.max(height,callingConvBox.getHeight());
			}
			if (alignmentBox.isVisible()) {
				if (alignmentBox.getWidth()==0 || alignmentBox.getHeight()==0) {
					alignmentBox.setSize(alignmentBox.getPreferredSize());
				}
				alignmentBox.setLocation(x,0);
				x += alignmentBox.getWidth();
				height = Math.max(height,alignmentBox.getHeight());
			}
			if (exportedBox!=null && exportedBox.isVisible()) {
				if (exportedBox.getWidth()==0 || exportedBox.getHeight()==0) {
					exportedBox.setSize(exportedBox.getPreferredSize());
				}
				exportedBox.setLocation(x,0);
				x += exportedBox.getWidth();
				height = Math.max(height,exportedBox.getHeight());
			}
			if (offsetField.isVisible()) {
				if (offsetField.getWidth()==0 || offsetField.getHeight()==0) {
					offsetField.setSize(offsetField.getPreferredSize());
				}
				offsetField.setLocation(x,0);
				x += offsetField.getWidth();
				height = Math.max(height,offsetField.getHeight());
			}
			setSize(x,height);
		}
		public int getMinWidth() {
			int width = 0;
			if (scopeBox.isVisible()) {
				if (scopeBox.getWidth()==0 || scopeBox.getHeight()==0) {
					scopeBox.setSize(scopeBox.getPreferredSize());
				}
				width += scopeBox.getWidth();
			}
			if (callingConvBox!=null && callingConvBox.isVisible()) {
				if (callingConvBox.getWidth()==0 || callingConvBox.getHeight()==0) {
					callingConvBox.setSize(callingConvBox.getPreferredSize());
				}
				width += callingConvBox.getWidth();
			}
			if (alignmentBox.isVisible()) {
				if (alignmentBox.getWidth()==0 || alignmentBox.getHeight()==0) {
					alignmentBox.setSize(alignmentBox.getPreferredSize());
				}
				width += alignmentBox.getWidth();
			}
			if (exportedBox!=null && exportedBox.isVisible()) {
				if (exportedBox.getWidth()==0 || exportedBox.getHeight()==0) {
					exportedBox.setSize(exportedBox.getPreferredSize());
				}
				width += exportedBox.getWidth();
			}
			if (offsetField.isVisible()) {
				if (offsetField.getWidth()==0 || offsetField.getHeight()==0) {
					offsetField.setSize(offsetField.getPreferredSize());
				}
				width += offsetField.getWidth();
			}
			return width;
		}
	}

	public final class CodePanelWrapper extends HeaderComponent implements ActionListener, ComponentListener {
		final CodePanel		codePanel;
		final CustomButton	expandButton;
		final CustomButton	collapseButton;
		boolean				isExpanded;
		public CodePanelWrapper(Header header) {
			super(header);
			// FIXME: This is not language-independent!!!!!!!!!!!
			codePanel = new ASMCodePanel((Function)code);
			codePanel.setBackground(BG_COLOUR.getColour());
			isExpanded = true;
			collapseButton = new ImageButton(Images.COLLAPSE_UP, Images.COLLAPSE_HOVER, Images.COLLAPSE_DOWN, Images.COLLAPSE_DISABLED);
			expandButton = new ImageButton(Images.EXPAND_UP, Images.EXPAND_HOVER, Images.EXPAND_DOWN, Images.EXPAND_DISABLED);
			collapseButton.addActionListener(this);
			expandButton.addActionListener(this);
			expandButton.setVisible(false);
			codePanel.addComponentListener(this);
			add(codePanel);
			add(collapseButton);
			add(expandButton);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_CODE_PANEL;
		}
		public void doLayout(int width) {
			collapseButton.setLocation(0,0);
			expandButton.setLocation(0,0);
			codePanel.removeComponentListener(this);
			codePanel.setBounds(0,0,width,codePanel.getHeight());
			codePanel.addComponentListener(this);
			setComponentZOrder(collapseButton,0);
			setSize(isExpanded ? codePanel.getSize() : expandButton.getSize());
		}
		public int getMinWidth() {
			return isExpanded ? codePanel.getMinimumSize().width : expandButton.getWidth();
		}
		public synchronized void actionPerformed(ActionEvent e) {
			isExpanded = !isExpanded;
			// NOTE: The codePanel may need to be set visible before the collapse button so that it has lower z-order.
			codePanel.setVisible(isExpanded);
			collapseButton.setVisible(isExpanded);
			expandButton.setVisible(!isExpanded);
			doFullLayout();
		}
		public void componentResized(ComponentEvent e) {
			if (isExpanded){
				super.componentResized(e);
			}
		}
	}

	public final class StructureMemberTable extends HeaderComponent {
		final ObservedStringTable<StructureMember>	membersField;
		public StructureMemberTable(Header header) {
			super(header);
			membersField = new ObservedStringTable<StructureMember>(code,"Member Variables:",((Structure)code).getMembers(),StructureMember.DUMMY,new String[]{"","DWORD","?",""});
			membersField.setFont(OTHER_FONT);
			membersField.setForeground(TEXT_COLOUR.getColour());
			membersField.setBackground(TRANSPARENT_COLOUR);
			add(membersField);
			membersField.addComponentListener(this);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_STRUCTURE_MEMBERS;
		}
		public void doLayout(int width) {
			membersField.removeComponentListener(this);
			membersField.setLocation(0,0);
			membersField.doLayout();
			membersField.addComponentListener(this);
			setSize(membersField.getSize());
		}
		public int getMinWidth() {
			return membersField.getWidth();
		}
	}

	public final class IncrementField extends HeaderComponent {
		final TextLabel		label;
		final LineField		increment;
		public IncrementField(Header header) {
			super(header);
			label = new TextLabel("Increment:",TRANSPARENT_COLOUR);
			increment = new LineField(((Constants)code).getIncrement(),code.getFile().getCaretRef(),false);
			increment.setBackground(TRANSPARENT_COLOUR);
			increment.setForeground(TEXT_COLOUR.getColour());
			increment.setBorder(TEXT_FIELD_BORDER);
			add(label);
			add(increment);
			label.setSize(label.getPreferredSize());
			increment.addComponentListener(this);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_INCREMENT;
		}
		public void doLayout(int width) {
			label.setLocation(0,0);
			increment.removeComponentListener(this);
			increment.setLocation(label.getWidth(),0);
			increment.setSize(increment.getPreferredSize());
			increment.addComponentListener(this);
			setSize(label.getWidth()+increment.getWidth(),Math.max(label.getHeight(),increment.getHeight()));
		}
		public int getMinWidth() {
			return label.getWidth()+increment.getWidth();
		}
	}

	public final class ConstantTable extends HeaderComponent {
		final ObservedStringTable<Constant>	membersField;
		public ConstantTable(Header header) {
			super(header);
			membersField = new ObservedStringTable<Constant>(code,"Constants:",((Constants)code).getMembers(),Constant.DUMMY);
			membersField.setFont(OTHER_FONT);
			membersField.setForeground(TEXT_COLOUR.getColour());
			membersField.setBackground(TRANSPARENT_COLOUR);
			add(membersField);
			membersField.addComponentListener(this);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_CONSTANTS_MEMBERS;
		}
		public void doLayout(int width) {
			membersField.removeComponentListener(this);
			membersField.setLocation(0,0);
			membersField.doLayout();
			membersField.addComponentListener(this);
			setSize(membersField.getSize());
		}
		public int getMinWidth() {
			return membersField.getWidth();
		}
	}

	public final class VariablePieceTable extends HeaderComponent {
		final ObservedStringTable<GlobalVariable.Piece>	membersField;
		public VariablePieceTable(Header header) {
			super(header);
			GlobalVariable variable = (GlobalVariable)code;
			String defaultType = variable.getDefaultType();
			String defaultValue = variable.getDefaultValue();
			membersField = new ObservedStringTable<GlobalVariable.Piece>(code,null,variable.getPieces(),GlobalVariable.Piece.DUMMY,new String[]{defaultType,defaultValue},0);
			membersField.setFont(OTHER_FONT);
			membersField.setForeground(TEXT_COLOUR.getColour());
			membersField.setBackground(TRANSPARENT_COLOUR);
			add(membersField);
			membersField.addComponentListener(this);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_VAR_PIECES;
		}
		public void doLayout(int width) {
			membersField.removeComponentListener(this);
			membersField.setLocation(0,0);
			membersField.doLayout();
			membersField.addComponentListener(this);
			setSize(membersField.getSize());
		}
		public int getMinWidth() {
			return membersField.getWidth();
		}
	}

	public final class SegmentField extends HeaderComponent {
		final TextLabel					label;
		final ObservedStringComboBox	segmentComboBox;
		final SingleLine.LineObserver	nameObserver;
		public SegmentField(Header header) {
			super(header);
			final Section section = (Section)code;
			label = new TextLabel("Segment:",TRANSPARENT_COLOUR);
			String[] segmentNames = code.getProject().getSegmentNamesArray();
			segmentComboBox = new ObservedStringComboBox(section.getSegmentName(),segmentNames);
			// NOTE: The combo boxes don't render properly with transparency
			segmentComboBox.setBackground(BG_COLOUR.getColour());//TRANSPARENT_COLOUR);
			segmentComboBox.setForeground(TEXT_COLOUR.getColour());
			segmentComboBox.setFont(OTHER_FONT);
			section.getProject().getSegments().addObserver(new ListObserver<Segment>() {
				public void listElementAdded(ObservedList<Segment> s, int index) {
					s.get(index).getName().addObserver(nameObserver);
					int selectedIndex = segmentComboBox.getSelectedIndex();
					segmentComboBox.setPossibleValues(section.getProject().getSegmentNamesArray());
					// NOTE: There is the default entry at index 0 of the combo box.
					if (index<=selectedIndex-1) {
						segmentComboBox.setSelectedIndex(selectedIndex+1);
					}
				}

				public void listElementRemoved(ObservedList<Segment> s, int index, Segment element) {
					element.getName().removeObserver(nameObserver);
					int selectedIndex = segmentComboBox.getSelectedIndex();
					segmentComboBox.setPossibleValues(section.getProject().getSegmentNamesArray());
					// NOTE: There is the default entry at index 0 of the combo box.
					if (index==selectedIndex-1) {
						segmentComboBox.setSelectedIndex(0);
					}
					else if (index<selectedIndex-1) {
						segmentComboBox.setSelectedIndex(selectedIndex-1);
					}
				}

				public void listElementSet(ObservedList<Segment> s, int index, Segment oldElement) {
					oldElement.getName().removeObserver(nameObserver);
					segmentComboBox.setPossibleValues(section.getProject().getSegmentNamesArray());
				}

				public void listElementMoved(ObservedList<Segment> s, int from, int to) {
					int selectedIndex = segmentComboBox.getSelectedIndex();
					segmentComboBox.setPossibleValues(section.getProject().getSegmentNamesArray());
					// NOTE: There is the default entry at index 0 of the combo box.
					if (from==selectedIndex-1) {
						segmentComboBox.setSelectedIndex(to+1);
					}
					else if (to>from && selectedIndex-1>from && selectedIndex-1<=to) {
						segmentComboBox.setSelectedIndex(selectedIndex-1);
					}
					else if (from>to && selectedIndex-1>=to && selectedIndex-1<from) {
						segmentComboBox.setSelectedIndex(selectedIndex+1);
					}
				}
			});
			nameObserver = new SingleLine.LineObserver() {
				public void valueChanged(SingleLine line, String oldValue) {
					segmentComboBox.setPossibleValues(section.getProject().getSegmentNamesArray());
				}
			};
			for (Segment segment : section.getProject().getSegments()) {
				segment.getName().addObserver(nameObserver);
			}

			add(label);
			add(segmentComboBox);
			segmentComboBox.addComponentListener(this);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_SEGMENT;
		}
		public void doLayout(int width) {
			if (label.getWidth()==0 || label.getHeight()==0) {
				label.setSize(label.getPreferredSize());
			}
			label.setLocation(0,0);
			if (segmentComboBox.getWidth()==0 || segmentComboBox.getHeight()==0) {
				segmentComboBox.setSize(segmentComboBox.getPreferredSize());
			}
			segmentComboBox.setLocation(label.getWidth(),0);
			setSize(label.getWidth()+segmentComboBox.getWidth(),Math.max(label.getHeight(),segmentComboBox.getHeight()));
		}
		public int getMinWidth() {
			return label.getWidth()+segmentComboBox.getWidth();
		}
	}

	public final class ContentPanel extends HeaderComponent implements ActionListener, ListObserver<CodeFileChunk> {
		final JComponent		contentPanel;
		final CustomButton		expandButton;
		final CustomButton		collapseButton;
		boolean					isExpanded;
		public ContentPanel(Header header) {
			super(header);
			Section section = (Section)code;
			contentPanel = new JPanel(null);
			contentPanel.setBackground(BG_COLOUR.getColour());
			isExpanded = true;
			collapseButton = new ImageButton(Images.COLLAPSE_UP, Images.COLLAPSE_HOVER, Images.COLLAPSE_DOWN, Images.COLLAPSE_DISABLED);
			expandButton = new ImageButton(Images.EXPAND_UP, Images.EXPAND_HOVER, Images.EXPAND_DOWN, Images.EXPAND_DISABLED);
			collapseButton.addActionListener(this);
			expandButton.addActionListener(this);
			expandButton.setVisible(false);
			ObservedList<CodeFileChunk> sectionContent = section.getContent();
			for (CodeFileChunk chunk : sectionContent) {
				Header aHeader = new Header(chunk);
				contentPanel.add(aHeader);
			}
			sectionContent.addObserver(this);
			add(contentPanel);
			add(collapseButton);
			add(expandButton);
			contentPanel.addComponentListener(this);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_CONTENT_PANEL;
		}
		public void doLayout(int width) {
			collapseButton.setLocation(0,0);
			expandButton.setLocation(0,0);
			int y = 0;
			for (int i=0;i<contentPanel.getComponentCount();++i) {
				Header header = (Header)contentPanel.getComponent(i);
				header.setLocation(0,y);
				header.doLayout(width);
				y += header.getHeight();
			}
			contentPanel.removeComponentListener(this);
			contentPanel.setBounds(0,collapseButton.getHeight(),width,y);
			contentPanel.addComponentListener(this);
			if (isExpanded) {
				setSize(width,collapseButton.getHeight()+contentPanel.getHeight());
			}
			else {
				setSize(expandButton.getSize());
			}
		}
		public int getMinWidth() {
			if (isExpanded) {
				int width = collapseButton.getWidth();
				for (int i=0;i<contentPanel.getComponentCount();++i) {
					Header header = (Header)contentPanel.getComponent(i);
					width = Math.max(width,header.getMinWidth());
				}
				return width;
			}
			return expandButton.getWidth();
		}
		public Header[] getContent() {
			Header[] headers = new Header[contentPanel.getComponentCount()];
			for (int i=0;i<headers.length;++i) {
				headers[i] = (Header)contentPanel.getComponent(i);
			}
			return headers;
		}
		public void actionPerformed(ActionEvent e) {
			setExpanded(!isExpanded);
		}
		public synchronized void setExpanded(boolean b) {
			if (isExpanded!=b) {
				isExpanded = b;
				// NOTE: The contentPanel may need to be set visible before the collapse button (as it is now) so that it has lower z-order.
				contentPanel.setVisible(isExpanded);
				collapseButton.setVisible(isExpanded);
				expandButton.setVisible(!isExpanded);
				doFullLayout();
			}
		}
		public void listElementAdded(ObservedList<CodeFileChunk> s, int index) {
			Header header = new Header(s.get(index));
			contentPanel.add(header,index);
			doFullLayout();
		}

		public void listElementRemoved(ObservedList<CodeFileChunk> s, int index, CodeFileChunk element) {
			contentPanel.remove(index);
			doFullLayout();
		}

		public void listElementSet(ObservedList<CodeFileChunk> s, int index, CodeFileChunk oldElement) {
			Header header = new Header(s.get(index));
			contentPanel.remove(index);
			contentPanel.add(header,index);
			doFullLayout();
		}

		public void listElementMoved(ObservedList<CodeFileChunk> s, int from, int to) {
			Header header = (Header)contentPanel.getComponent(from);
			contentPanel.remove(from);
			contentPanel.add(header,to);
			doFullLayout();
		}

		public void componentResized(ComponentEvent e) {
			if (isExpanded) {
				if (collapseButton.getHeight()+((Component)e.getSource()).getHeight()!=getHeight() || getMinWidth()>Header.this.getWidth()-MARGIN*2) {
					doFullLayout();
				}
				else {
					doLayout(Header.this.getWidth());
				}
			}
		}
	}

	public final class AuthorTable extends HeaderComponent {
		final ObservedStringTable<Rowable>	authorsField;
		public AuthorTable(Header header) {
			super(header);
			authorsField = new ObservedStringTable<Rowable>(code,"Authors:",((CodeFile)code).getAuthors(),Rowable.AUTHORS_DUMMY);
			authorsField.setFont(OTHER_FONT);
			authorsField.setForeground(TEXT_COLOUR.getColour());
			authorsField.setBackground(TRANSPARENT_COLOUR);
			add(authorsField);
			authorsField.addComponentListener(this);
			setBackground(TRANSPARENT_COLOUR);
			setOpaque(false);
		}
		public int getType() {
			return LOCATION_AUTHORS;
		}
		public void doLayout(int width) {
			authorsField.removeComponentListener(this);
			authorsField.setLocation(0,0);
			authorsField.doLayout();
			authorsField.addComponentListener(this);
			setSize(authorsField.getSize());
		}
		public int getMinWidth() {
			return authorsField.getWidth();
		}
	}
}
